/*
  Copyright (C) 2002, 2010  Dmitry V. Levin <ldv@altlinux.org>

  The sed-based in-place files editor, a wrapper around
  sed -i --follow-symlinks.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <utime.h>
#include <sys/stat.h>
#include <sys/wait.h>

static struct option const longopts[] = {
	{"preserve", no_argument, 0, 'p'},
	{"help", no_argument, 0, 'h'},
	{0, no_argument, 0, 0}
};

static void __attribute__ ((__noreturn__)) usage(FILE * stream, int status)
{
	fprintf(stream, "\
%s - the sed-based in-place files editor.\n\
Usage: %s <pattern> [-p | --preserve] files...\n\
       %s [-h | --help]\n\
\nReport bugs to http://bugs.altlinux.ru/\n",
	program_invocation_short_name,
	program_invocation_short_name,
	program_invocation_short_name);
	exit(status);
}

static int
sed(const char *pattern, const char *fname)
{
	pid_t   pid = fork();

	if (pid < 0)
	{
		error(EXIT_SUCCESS, errno, "fork");
		return EXIT_FAILURE;
	}

	if (!pid)
	{
		/* Child */
		const char *const args[] =
			{ "subst: sed", "-i", "--follow-symlinks", "-e", pattern, "--", fname, NULL };

		execvp("sed", (char *const *) args);
		error(EXIT_FAILURE, errno, "execvp: sed");
	} else
	{
		/* Parent */
		int     status;

		if (TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)) != pid)
		{
			error(EXIT_SUCCESS, errno, "waitpid: %d", pid);
			return EXIT_FAILURE;
		}

		if (!WIFEXITED(status) || WEXITSTATUS(status))
			return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

static int
subst(const char *pattern, const char *fname)
{
	struct stat st;

	if (stat(fname, &st) < 0)
	{
		error(EXIT_SUCCESS, errno, "stat: %s", fname);
		return EXIT_FAILURE;
	}

	if (sed(pattern, fname) != EXIT_SUCCESS)
		return EXIT_FAILURE;

	struct utimbuf utm = { st.st_atime, st.st_mtime };

	if (utime(fname, &utm) < 0)
		error(EXIT_SUCCESS, errno, "utime: %s", fname);
	/* ignore possible error */

	return EXIT_SUCCESS;
}

int
main(int ac, char *const av[])
{
	int     c;
	int     preserve = 0;
	const char *pattern;

	while ((c = getopt_long(ac, av, "ph", longopts, 0)) != -1)
		switch (c)
		{
			case 'p':
				preserve = 1;
				break;
			case 'h':
				usage(stdout, EXIT_SUCCESS);

			default:
				usage(stderr, EXIT_FAILURE);
		}

	if (optind >= ac)
	{
		error(EXIT_SUCCESS, 0, "no pattern specified.");
		usage(stderr, EXIT_FAILURE);
	}

	pattern = av[optind++];

	if (optind >= ac)
	{
		error(EXIT_SUCCESS, 0, "no files specified.");
		usage(stderr, EXIT_FAILURE);
	}

	if (!preserve)
	{
		size_t  nargs = ac - optind;
		size_t  args_size = nargs + 7;
		const char **args = calloc(args_size, sizeof(*args));

		if (!args)
			error(EXIT_FAILURE, errno, "calloc");
		args[0] = "subst: sed";
		args[1] = "-i";
		args[2] = "--follow-symlinks";
		args[3] = "-e";
		args[4] = pattern;
		args[5] = "--";
		memcpy(args + 6, av + optind, nargs * sizeof(*args));
		args[args_size - 1] = NULL;
		execvp("sed", (char *const *) args);
		error(EXIT_FAILURE, errno, "execvp: sed");
	}

	while (optind < ac)
		if (subst(pattern, av[optind++]) != EXIT_SUCCESS)
			return EXIT_FAILURE;

	return EXIT_SUCCESS;
}
